/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */

/**
 * type: 'ajax',
 * Ext JS 定制
 *
 * grid 动态列
 * grid.headerCt.insert(grid.headerCt.getGridColumns().length, {header:'xx'})
 * grid.getView().refresh()
 *
 * @author vacoor
 */
Ext.define('Nothing.util.SecurityUtils', {

    statics: {
        PERMISSIONS: [],

        hasPermission: function (permission) {
            for (var i = 0; i < this.PERMISSIONS.length; i++) {
                var perm = this.PERMISSIONS[i]
                if (perm === permission) {
                    return perm;
                }
            }
        }
    }
});

// TODO TEMP
Ext.define('Nothing.custom.data.Field', {
    override: 'Ext.data.Field',

    constructor: function (config) {
        var me = this;

        me.callParent(arguments);

        // 参看 doTransformIf
        if (!me.hasCustomConvert && config.transformIf) {
            me.convert = function (values) {
                return me.doTransformIf(values, me.transformIf, me.transformDelimiter)
            };
            me.hasCustomConvert = true;
        }
    },

    /**
     * 如果给定对象/数组 不是基本类型(或基本类型数组)则提取给定属性, 否则直接返回
     * eg:
     * transformIf(1, '') --> 1
     * transformIf([1,2], '') --> [1,2]
     * transformIf( [ {partNo: '1'}, {partNo: '2'} ], 'partNo') --> ['1', '2']
     * transformIf({partNo: '123'}, 'partNo') --> '123'
     *
     * 主要用于
     * {name: 'xx', transform: 'object array', convert: function(values, record) {} }
     * 如果mapping 是一个对象, 在修改值时(record.set) 可能传递只是基本类型,
     * 但两次都会调用 convert, 都由参数 values 接收
     * 一个对象, 一个是基本数据类型(及其数组), 这里方便在 Field 中调用
     * (主要针对 M 对 N, 一对一就不需要了可以 transform: 'parent.id', 不需要convert)
     *
     * @param values
     * @param name
     * @returns {*}
     */
    doTransformIf: function (values, name, delimiter) {
        if (Ext.isPrimitive(values)) {
            return values;
        } else if (Ext.isArray(values)) {
            var vs = [];
            Ext.each(values, function (value) {
                vs.push(Ext.isPrimitive(value) ? value : value[name]);
            });

            return delimiter ? vs.join(delimiter) : vs;
        } else if (Ext.isObject(values)) {
            return values[name];
        } else {
            return values;
        }
    }
});

/***********************
 ******** 默认值
 */
Ext.define('Nothing.custom.data.Store', {
    override: 'Ext.data.Store',

    /**
     * 默认使用远程排序
     */
    pageSize: 30,
    remoteFilter: true,
    remoteSort: true,

    /**
     * {@link Ext.data.Store#onProxyLoad}
     * @override
     */
    onProxyLoad: function (operation) {
        var me = this,
            records = operation.getRecords();

        if (!me.isDestroyed) {
            // TODO 这里根据需要做个预处理,eg: 对于不分页的数据做个过滤
            // records.splice(10, 10);
        }

        me.callParent(arguments);
    },

    exp: function (options) {
        var me = this,
            operation;

        options = Ext.apply({
            action: 'exp',
            filters: me.filters.items,
            sorters: me.getSorters()/*,
             allowWrite: function () {
             return this.action != 'read' && this.action != 'exp';
             }
             */
        }, options);

        operation = new Ext.data.Operation(options);

        if (me.fireEvent('beforeexp', me, operation) !== false) {
            me.loading = true;
            me.proxy.exp(operation, me.onProxyExp, me);
        }

        return me;
    },

    onProxyExp: Ext.emptyFn
});

Ext.define('Nothing.custom.toolbar.Paging', {
    override: 'Ext.toolbar.Paging',

    plugins: ['pagingtoolbarresizer'],   // ----- TODO 加载这里是否合适

    displayInfo: true,
    border: false
});

Ext.define('Nothing.custom.ux.PagingToolbarResize', {
    override: 'Ext.ux.PagingToolbarResizer',

    options: [10, 15, 25, 30, 50, 75, 100, 200]
});

Ext.define('Nothing.custom.grid.plugin.CellEditing', {
    override: 'Ext.grid.plugin.CellEditing',

    clicksToEdit: 1 // 单击
});

Ext.define('Nothing.custom.grid.plugin.RowEditing', {
    override: 'Ext.grid.plugin.RowEditing',

    clicksToEdit: 2,
    clicksToMoveEditor: 2,
    autoCancel: true,
    errorSummary: false,    // 不显示错误提示, 否则会有两个错误提示

    startEdit: function (record, columnHeader) {
        var me = this;

        if (me.isDisabled()) {
            return false;
        }
        return me.callParent(arguments);
    },

    isDisabled: function () {
        return !!this.disabled;
    },

    setDisabled: function (disabled) {
        return this.disabled = disabled;
    },

    /*
     * 不能覆盖, 每次发送请求后相应grid 会刷新，从而触发该方法
     * 千万不要在这里 rejectChanges() 当多个修改时候, 发送多个请求
     * 如果rejectChange 后面请求的提交的数据丢失,
     * edit 完成后会派发edit事件, 但是不会派发 cancel 事件..
     */
    cancelEdit: function () {
        var me = this,
            context = me.context,
            record = context.record,
            editing = me.editing,
            store = context.store;

        me.callParent(arguments);
        if (editing) {
            // 新增取消
            store.filterNewOnly(record) && store.remove(record);
        }
    },

    /**
     * {@link Ext.grid.plugin.RowEditing#completeEdit}
     * @override
     */
    completeEdit: function () {
        var me = this;

        if (me.editing && me.validateEdit()) {
            me.editing = false;

            // add
            me.updateRawValueField(me, me.context);

            me.fireEvent('edit', me, me.context);
        }
    },

    /**
     * 允许主从更新, value 更新 record field 时, rawValue 也更新 record field
     * 对于多选, 如果editor multiSelect = true 并且 delimiter 存在, 则将 rawValue.split(delimiter)
     *
     * (后面一段废话，可以直接跳到后面)
     * 在 Grid 中 可能存在某列类似 combo, 显示 name(一般仅用于展示, 不持久), 但是 editor 修改的是对应的 id
     * (这种不一致, 可以通过 column tpl, renderer, 或 editor name 配置)
     * 例如:
     * columns: [
     *    {
     *        header: '父节点',
     *        listConfig: {
     *           getInnerTpl: function () {
     *              return '<div data-qtip="{parentName}. {parentId}">{parentName} ({parentId})</div>';
     *           }
     *        },
     *        dataIndex: 'parentName',  // 这里主要用于排序
     *        editor: {name: parentId, xtype:'combo', ..}
     *        ...
     *    }
     * ]
     *
     * 数据 [{id: '1', name: '节点1'}, {id: '2', name: '节点2'}]
     *
     * 当 id 由 1 --> 2 后如果没有提交刷新, rowediting 只会更行editor name指定的值(读取也从该属性读取, 默认为dataIndex)
     * 因此上面 name 是不会变化的, 就会产生 "节点1(2)"
     *
     * Text 提供两个方法
     * {@link Ext.form.field.Text#getRawValue} 用于显示, {@link Ext.form.field.Text#getValue} 用于提交
     *
     * --------------  分割线 ----------
     * roweding 默认为只取 getValue 值来更新 dataIndex(或editor name) 对应值
     *
     * 这里进行扩充允许 rawValue 也更新 field, 需要给editor 配置 rawValueUpdateField
     *
     * eg: 修改上面 editor: { xtype: 'combo', rawValueUpdateField: 'parentName', ... }
     *
     * 当 id 1 -- 2 时, 会可以 节点2(2), 而不是 节点1(2) 了
     *
     * 注: 这个更新是在编辑后执行, 当一下三个条件同时成立才会触发
     *    1. editor 值被修改 (可视为主从变化)
     *    2. 存在 getRawValue function (eg: Text 子类)
     *    2. 指定了rawValueUpdateField  (一般该字段应该是 persist = false)
     *    3. 没有 editor 修改过 rawValueUpdateField 指定值
     *
     * @private
     */
    updateRawValueField: function (me, context) {
        var editor = me.editor,
            editors = editor.query('>[isFormField][getRawValue][rawValueUpdateField]'),
            newValues = context.newValues,
            originalValues = context.originalValues,
            record = context.record,
            i, name, rawValueUpdateField, et;

        for (i = 0; i < editors.length; i++) {
            name = editors[i].name;
            // 如果value 值没有被修改过
            if (originalValues[name] == newValues[name]) {
                continue;
            }

            name = editors[i].rawValueUpdateField;
            if (name && !newValues.hasOwnProperty(name)) {
                et = editors[i];

                // 处理 combo / 自定义 treepicker 多选
                if (et.multiSelect && et.delimiter) {
                    record.set(name, et.getRawValue().split(et.delimiter));
                } else {
                    record.set(name, et.getRawValue());
                }
            }
        }
    }
});

Ext.define('Nothing.custom.grid.RowEditor', {
    override: 'Ext.grid.RowEditor',

    /**
     * 默认提供了 onFieldChange 处理方法
     * 但是并没有添加事件监听, 内容改变后没有重新校验更新 Update 按钮
     */
    addFieldsForColumn: function (column, initial) {
        var me = this, i, length;

        me.callParent(arguments);
        if (Ext.isArray(column)) {
            for (i = 0, length = column.length; i < length; i++) {
                me.addChangeListenerForColumn(column[i]);
            }
            return;
        }
        me.addChangeListenerForColumn(column);
    },

    addChangeListenerForColumn: function (column) {
        var me = this, field;

        if (column.getEditor) {
            field = column.getEditor(null, {
                xtype: 'displayfield',
                getModelData: function () {
                    return null;
                }
            });
            me.mon(field, 'change', me.onFieldChange, me);
        }
    }
});

Ext.define('Nothing.custom.form.field.Base', {
    override: 'Ext.form.field.Base',

    labelAlign: 'left', // 会影响 column 布局的方向不要改变，在formpanel里面设置defaults{ labelAlign}
    msgTarget: 'side' // 'qtip'
});

Ext.define('Nothing.custom.data.Field', {
    override: 'Ext.data.Field',

    dataFormat: 'Y-m-d'
});

Ext.define('Noting.custom.grid.column.Date', {
    override: 'Ext.grid.column.Date',

    format: 'Y-m-d'
});

Ext.define('Nothing.custom.form.field.Date', {
    override: 'Ext.form.field.Date',

    format: 'Y-m-d',
    //submitFormat: 'Y-m-d'
    submitFormat: 'U'   // 毫秒
});

Ext.define('Nothing.custom.form.field.Checkbox', {
    override: 'Ext.form.field.Checkbox',

    uncheckedValue: 0,
    inputValue: 1
});

Ext.define('Nothing.custom.form.field.ComboBox', {
    override: 'Ext.form.field.ComboBox',

    editable: false, // 大多应该都是不可编辑的吧?!
    delimiter: ',',  //', '
    displayField: 'name', //'text'
    valueField: 'id',
    queryParam: 'query',
    allQuery: '',

    /**
     * 这里只放在 combobox 中, 也可以放在{@link Ext.data.Store#onProxyLoad}
     * 不过感觉不好
     * @param queryPlan
     */
    afterQuery: function (queryPlan) {
        var me = this,
            all = {};

        if (me.enableAllOption) {       // 增加所有选项
            all[me.valueField] = '';
            all[me.displayField] = '全部';
            me.store.insert(0, all);
        }
        me.callParent(arguments);
    }
});

/*
Ext.define('Nothing.custom.tree.Panel', {
    override: 'Ext.tree.Panel',

    animate: false,
    displayField: 'name' //'text'
});
*/

/**********************
 ******* 默认行为
 **********************/

/**
 * 定制所有组件初始化行为，处理权限
 */
Ext.define('Nothing.custom.Component', {
    override: 'Ext.Component',

    initComponent: function () {
        var me = this;
        var permission = me.permission;

        if (Ext.isDefined(permission)) {
            var hidden = !Nothing.util.SecurityUtils.hasPermission(permission);
            me.hidden = me.hidden || hidden;
            me.disabled = me.disabled || hidden;
        }

        return this.callParent(arguments);
    }
});

/**
 * grid处理, 以便重用 store
 */
Ext.define('Nothing.custom.grid.Panel', {
    override: 'Ext.grid.Panel',

    columnLines: true,  //竖直线

    beforeRender: function () {
        var me = this,
            store = me.store;

        // 不是第一次加载数据, 第一次 autoLoad 时已经加载, 防止重复发送请求
        if (store.autoLoad && store.data.indexGeneration > 1) {
            // 不使用缓存
            delete store.totalCount;
            store.currentPage = 1;
            store.load();
        }

        me.callParent(arguments);
    },

    beforeDestroy: function () {
        var me = this,
            store = me.store;

        // 清空数据
        store.data.clear();
        store.filters.clear();
        store.sorters.clear();
        store.groupers.clear();

        delete store.currentPage;
        delete store.pageSize;

        Ext.destroy(me.getSelectionModel());
        me.selModel = null;
        me.callParent(arguments);
    }
});

/**
 * 覆盖 TreeView, 单击展开节点
 */
Ext.define('Nothing.custom.tree.View', {
    override: 'Ext.tree.View',

    /**
     * @override
     */
    onItemClick: function (record, item, index, e) {
        var me = this;

        /* 单击 cell (非+, checkbox) 时 展开 */
        if (me.panel.singleClickExpand && record.isExpandable() && e.getTarget(me.cellSelector, item)) {
            me.toggle(record, e.ctrlKey);
            return false;
        }
        return this.callParent(arguments);
    },

    onItemKeyDown: function (record, item, index, e) {
        var me = this,
            key = e.getKey();

        // ENTER --> click
        if (e.ENTER == key) {
            e.type = 'click';
            me.fireEvent('itemclick', me, record, item, index, e);
            return false;
        }
    }
});

/*- Json 重新定义了 root 属性, 覆盖 Reader 将无法被 json 继承这个覆盖
 Ext.define('Nothing.custom.data.reader.Reader', {
 override: 'Ext.data.reader.Reader',
 */
/*- 不要覆盖, 可能会影响默认组件处理
 Ext.define('Nothing.custom.data.reader.Json', {
 override: 'Ext.data.reader.Json',

 readRecordsOnFailure: false, // 当失败时不读取 records
 successProperty: 'success', // 只有当 === false || === 'false' 才认为false
 messageProperty: 'message',
 totalProperty: 'recordCount',
 root: 'data'
 });
 */

/**
 * 定制 默认 ServerProxy
 */
Ext.define('Nothing.custom.data.proxy.Server', {
    override: 'Ext.data.proxy.Server',

    pageParam: 'page',
    startParam: '',     // 没必要
    limitParam: 'size',
    sortParam: 'rawsort',
    filterParam: '',    // 这里去掉 filter 参数, 暂时觉得格式不爽

    batchOrder: 'create,update,destroy', // 批量处理不同操作时提交顺序, 多个处理前一个才会处理下一个
    batchActions: false,     // 不允许相同操作合并请求,(允许批量操作, 将作为一个数组只发送一个请求)
    defaultReaderType: 'json',

    /**
     * 具体请求方式参见 {@link Ext.data.proxy.Ajax#actionMethods}
     */
    // reader: {},

    // 请求设置
    writer: {
        /**
         * 不应该使用mapping用于告诉reader name 属性从mapping获取, 但是提交时不一定是提交到mapping
         * eg: name: parentId, transform: 'parent', convert: ... 但是提交时参数不是parent, 而是parentId
         * 当然这里也可以自定一个一个属性, eg: submitName
         */
        //nameProperty: 'transform',  // 提交到服务端时的名称使用model属性,
        writeAllFields: false,  // 只提交更新字段
        // for json writer
        root: '',               // 提交的 record 属性 eg: {age: 10} 当指定 data 则包装为 {data: {age:10}}
        encode: false,          // 当指定 root 时(必须)将json编码为root值(root=)
        expandData: false,       // 写入时是否展开"." 'foo.bar.prop' --> foo: { bar: { prop: xxx }}
        allowSingle: true // 允许以单个对象形式提交, 参考 proxy batchActions
    },

    /**
     * {@link Ext.data.proxy.Ajax#doRequest}
     */
    exp: function (operation, callback, scope) {
        // return this.doRequest.apply(this, arguments);
        var me = this,
            request = me.buildRequest(operation);

        Ext.MessageBox.alert('导出', 'TODO EXPORT, params:' + Ext.encode(request.params));
    },

    /*
     * 添加 operation, fieldType
     * @override
     */
    encodeFilters: function (filters) {
        var min = [],
            length = filters.length,
            i = 0;

        for (; i < length; i++) {
            min[i] = {
                property: filters[i].property,
                operation: filters[i].operation || 'EQ',
                fieldType: filters[i].fieldType || 'S',
                value: filters[i].value
            };
        }
        return this.applyEncoding(min);
    },

    /*
     encodeSorters: function (sorters) {
     var sort = "";
     for (var i = 0; i < sorters.length; i++) {
     if (i > 0) {
     sort += ',';
     }
     sort += sorters[i].property + '-' + sorters[i].direction
     }

     //return this.applyEncoding(sort);
     return sort;
     }
     */

    /**
     * @override
     */
    getParams: function (operation) {
        var me = this;

        me.prepareOperation(operation);
        return me.callParent(arguments);
    },

    /*
     * 这里是放在请求附加参数中了, 也可以操作 getParams 的返回值
     * @private
     */
    prepareOperation: function (operation) {
        var filters = operation.filters || [];
        var sorters = operation.sorters || [];

        // 不能使用 proxy.extraParams 它是永久的, 每次都会发送
        // var extraParams = store.proxy.extraParams;
        var requestExtraParams = operation.params = operation.params || {};

        for (var i = 0; i < filters.length; i++) {
            var key = 'search_' + filters[i].property;

            if (filters[i].operation) {
                key += '_' + filters[i].operation;
            }
            if (filters[i].fieldType) {
                key += '_' + filters[i].fieldType;
            }

            requestExtraParams[key] = filters[i].value;
        }

        var sort = '';
        for (var i = 0; i < sorters.length; i++) {
            if (i > 0) {
                sort += ',';
            }
            sort += sorters[i].property + '-' + (sorters[i].direction || 'ASC');
        }
        if (sort) {
            requestExtraParams['sort'] = sort;
        }
    },

    listeners: {
        // TODO
        exception: function (proxy, response, operation) {
            // 尽量不去访问原始的相应了， 降低下偶和吧
            // var json = response.responseText ? Ext.decode(response.responseText) : response;
            var msg = 200 == response.status ? operation.error : operation.error.status + operation.error.statusText;
            //Ext.Error.raise('Error:' + msg);
            Ext.MessageBox.alert('错误', msg);
        }
    }
});

/**
 * FiltersFeature
 */
Ext.define('Nothing.custom.ux.grid.FiltersFeature', {
    override: 'Ext.ux.grid.FiltersFeature',

    paramPrefix: 'search',

    /**
     * @override
     */
    buildQuery: function (filters) {
        var p = {}, i, f, root, dataPrefix, key, tmp;

        for (var i = 0; i < filters.length; i++) {
            f = filters[i];
            root = this.paramPrefix + "_" + f.field;

            if (f.data.comparison) {
                root += "_" + f.data.comparison;
            }

            if (f.data.type) {
                root += "_" + f.data.type;
            }

            p[root] = f.data.value;
        }

        return p;
    }
});
